package cn.tedu.submarine;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Timer;
import java.util.*;

/**
 * 整个游戏世界
 */
public class World extends JPanel { //2.
    public static final int WIDTH = 641;
    /**
     * 窗口的宽
     */
    public static final int HEIGHT = 479;
    /**
     * 窗口的高
     */

    /**
     * 运行状态
     */
    public static final int RUNNING = 0;
    /**
     * 游戏结束状态
     */
    public static final int GAME_OVER = 1;
    /**
     * 等待客户端
     */
    private int state = RUNNING; //当前状态(默认为运行状态)

    /**
     * 为联机版本新添加的属性
     */
    public static final String CODE_SHOOT = "shoot";
    public static final String CODE_MOVE_LEFT = "left";
    public static final String CODE_MOVE_RIGHT = "right";

    private boolean isSingle = true;//是否单机
    private boolean isServer = false;//是否主机
    //作为主机需要的NIO属性
    //打开选择器
    private Selector selector;
    //打开TCP服务端通道
    private ServerSocketChannel serverSocketChannel;
    ByteBuffer buffer = ByteBuffer.allocate(1024*10);

    //作为从机需要的NIO属性
    private SocketChannel clientSocketChannel;
    private SyncInfo syncInfo;


    //如下这一堆为窗口中所显示的对象
    private Battleship ship = new Battleship(); //战舰
    private SeaObject[] submarines = {};        //潜艇(侦察潜艇、鱼雷潜艇、水雷潜艇)数组
    private Mine[] mines = {};                  //水雷数组
    private Bomb[] bombs = {};                  //深水炸弹数组

    //2p信息
    private Battleship ship_2p = new Battleship();;
    private Bomb[] bombs_2p = {};


    /**
     * 生成潜艇(侦察潜艇、鱼雷潜艇、水雷潜艇)对象
     */
    public SeaObject nextSubmarine() {
        Random rand = new Random(); //随机数对象
        int type = rand.nextInt(20); //0到19
        if (type < 10) { //0到9时，返回侦察潜艇对象
            return new ObserveSubmarine();
        } else if (type < 11) { //10到14，返回鱼雷潜艇对象
            return new TorpedoSubmarine();
        } else { //15到19时，返回水雷潜艇对象
            return new MineSubmarine();
        }
    }

    private int subEnterIndex = 0; //潜艇入场计数

    /**
     * 潜艇(侦察潜艇、鱼雷潜艇、水雷潜艇)入场
     */
    public void submarineEnterAction() { //每10毫秒走一次
        subEnterIndex++; //每10毫秒增1
        if (subEnterIndex % 40 == 0) { //每400(40*10)毫秒走一次
            SeaObject obj = nextSubmarine(); //获取潜艇对象
            submarines = Arrays.copyOf(submarines, submarines.length + 1); //扩容
            submarines[submarines.length - 1] = obj; //将obj添加到submarines的最后一个元素上
        }
    }

    private int mineEnterIndex = 0; //水雷入场计数

    /**
     * 水雷入场
     */
    public void mineEnterAction() { //每10毫秒走一次
        mineEnterIndex++; //每10毫秒增1
        if (mineEnterIndex % 100 == 0) { //每1秒(100*10)走一次
            for (int i = 0; i < submarines.length; i++) { //遍历所有潜艇
                if (submarines[i] instanceof MineSubmarine) { //若潜艇为水雷潜艇
                    MineSubmarine ms = (MineSubmarine) submarines[i]; //将潜艇强转为水雷潜艇类型
                    Mine obj = ms.shootMine(); //获取水雷对象
                    mines = Arrays.copyOf(mines, mines.length + 1); //扩容
                    mines[mines.length - 1] = obj; //将obj添加到最后一个元素上
                }
            }
        }
    }

    /**
     * 海洋对象移动
     */
    public void moveAction() { //每10毫秒走一次
        for (int i = 0; i < submarines.length; i++) { //遍历所有潜艇
            submarines[i].move(); //潜艇移动
        }
        for (int i = 0; i < mines.length; i++) { //遍历所有水雷
            mines[i].move(); //水雷移动
        }
        for (int i = 0; i < bombs.length; i++) { //遍历所有深水炸弹
            bombs[i].move(); //深水炸弹移动
        }
        //联机
        if (!isSingle && bombs_2p != null) {
            for (int i = 0; i < bombs_2p.length; i++) { //遍历所有深水炸弹
                bombs_2p[i].move(); //深水炸弹移动
            }
        }
    }

    /**
     * 删除越界的海洋对象---避免内存泄漏
     */
    public void outOfBoundsAction() { //每10毫秒走一次
        for (int i = 0; i < submarines.length; i++) { //遍历所有潜艇
            if (submarines[i].isOutOfBounds() || submarines[i].isDead()) { //越界了或者死了的
                submarines[i] = submarines[submarines.length - 1];
                submarines = Arrays.copyOf(submarines, submarines.length - 1); //缩容(缩掉最后一个元素)
            }
        }

        for (int i = 0; i < mines.length; i++) { //遍历所有水雷
            if (mines[i].isOutOfBounds() || mines[i].isDead()) { //越界了或者死了的
                mines[i] = mines[mines.length - 1]; //将越界水雷替换为最后一个元素
                mines = Arrays.copyOf(mines, mines.length - 1); //缩容(缩掉最后一个元素)
            }
        }

        for (int i = 0; i < bombs.length; i++) { //遍历所有深水炸弹
            if (bombs[i].isOutOfBounds() || bombs[i].isDead()) { //越界了或者死了的
                bombs[i] = bombs[bombs.length - 1]; //将越界炸弹替换为最后一个元素
                bombs = Arrays.copyOf(bombs, bombs.length - 1); //缩容(缩掉最后一个元素)
            }
        }
        if (!isSingle && bombs_2p != null) {
            for (int i = 0; i < bombs_2p.length; i++) { //遍历所有深水炸弹
                if (bombs_2p[i].isOutOfBounds() || bombs_2p[i].isDead()) { //越界了或者死了的
                    bombs_2p[i] = bombs_2p[bombs_2p.length - 1]; //将越界炸弹替换为最后一个元素
                    bombs_2p = Arrays.copyOf(bombs_2p, bombs_2p.length - 1); //缩容(缩掉最后一个元素)
                }
            }
        }
    }

    private int score = 0; //玩家的得分

    /**
     * 深水炸弹与潜艇的碰撞
     */
    public void bombBangAction() { //每10毫秒走一次
        bombBangAction_1(bombs,ship);
        if (!isSingle && bombs_2p != null) {
            bombBangAction_1(bombs_2p,ship_2p);
        }
    }

    public void bombBangAction_1(Bomb[] bombs,Battleship ship) { //每10毫秒走一次
        for (int i = 0; i < bombs.length; i++) { //遍历所有深水炸弹
            Bomb b = bombs[i]; //获取每个深水炸弹
            for (int j = 0; j < submarines.length; j++) { //遍历所有潜艇
                SeaObject s = submarines[j]; //获取每个潜艇
                if (b.isLive() && s.isLive() && s.isHit(b)) { //若都活着并且还撞上了
                    s.goDead(); //潜艇去死
                    b.goDead(); //深水炸弹去死
                    if (s instanceof EnemyScore) { //若被撞对象是分
                        EnemyScore es = (EnemyScore) s; //将被撞对象强转为得分接口
                        score += es.getScore(); //玩家得分
                    }
                    if (s instanceof EnemyLife) { //若被撞对象是命
                        EnemyLife ea = (EnemyLife) s; //将被撞对象强转为得命接口
                        int num = ea.getLife(); //获取命数
                        ship.addLife(num); //战舰增命
                    }
                }
            }
        }
    }


    /**
     * 水雷与战舰的碰撞
     */
    public void mineBangAction() { //每10毫秒走一次
        for (int i = 0; i < mines.length; i++) { //遍历所有水雷
            Mine m = mines[i]; //获取每一个水雷
            if (m.isLive() && ship.isLive() && m.isHit(ship)) { //若都活着并且撞上了
                m.goDead(); //水雷去死
                ship.subtractLife(); //战舰减命
            }
            if (!isSingle && ship_2p != null && m.isLive() && ship_2p.isLive() && m.isHit(ship_2p)) { //若都活着并且撞上了
                m.goDead(); //水雷去死
                ship_2p.subtractLife(); //战舰减命
            }
        }
    }

    /**
     * 检测游戏结束
     */
    public void checkGameOverAction() { //每10毫秒走一次
        if (isSingle) {
            if (ship.getLife() <= 0) { //若战舰的命数<=0，表示游戏结束了
                state = GAME_OVER; //则将当前状态修改为GAME_OVER游戏结束
            }
        } else {
            if(ship.getLife()<=0){
                ship.goDead();
            }
            if(ship_2p.getLife()<=0){
                ship_2p.goDead();
            }
            if (ship.getLife() <= 0 && ship_2p.getLife() <= 0) { //若战舰的命数<=0，表示游戏结束了
                state = GAME_OVER; //则将当前状态修改为GAME_OVER游戏结束
            }
        }
    }

    /**
     * 处理从机发送过来的消息
     * @param buffer
     */
    public void proccessClientMessage(ByteBuffer buffer){
        buffer.flip();
        byte[] arr = new byte[buffer.limit()];
        buffer.get(arr);
        buffer.clear();
        String code = new String(arr, StandardCharsets.UTF_8);
        System.out.println(code);
        switch (code){
            case CODE_MOVE_RIGHT:
                ship_2p.moveRight();
                break;
            case CODE_MOVE_LEFT:
                ship_2p.moveLeft();
                break;
            case CODE_SHOOT:
                if(ship_2p.isLive()) {
                    Bomb obj = ship_2p.shoot(); //获取深水炸弹对象
                    bombs_2p = Arrays.copyOf(bombs_2p, bombs_2p.length + 1); //扩容
                    bombs_2p[bombs_2p.length - 1] = obj; //将obj添加到bombs的最后一个元素上
                }
        }
    }
    /**
     * 处理主机发送过来的消息
     * @param buffer
     */
    public void proccessServerMessage(ByteBuffer buffer){
        buffer.flip();
        buffer.mark();
        int dataLen = buffer.getInt();
        if(buffer.position()+dataLen<=buffer.limit()){
            byte[] data = new byte[dataLen];
            buffer.get(data);
            try (
                    ByteArrayInputStream in = new ByteArrayInputStream(data);
                    ObjectInputStream ois = new ObjectInputStream(in);
            ){
                syncInfo=(SyncInfo) ois.readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            buffer.compact();
        }else{
            buffer.reset();
            buffer.compact();
        }
    }

    /**
     * 与从机同步状态信息
     */
    public void syncInfoToClient(){
        //向客户端同步消息
        try (
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(out);
        ){
            oos.writeObject(new SyncInfo(ship,submarines,mines,bombs,ship_2p, bombs_2p,state,score));
            byte[] data = new byte[out.size()+4];
            System.arraycopy(out.toByteArray(),0,data,4,out.size());
            data[0] = (byte)((data.length-4)>>>24);
            data[1] = (byte)((data.length-4)>>>16);
            data[2] = (byte)((data.length-4)>>>8);
            data[3] = (byte)((data.length-4));
            clientSocketChannel.write(ByteBuffer.wrap(data));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }

    public void doNetWork() {
        if (isSingle) {
            return;
        }
        try {
            selector.selectNow();
            Set<SelectionKey> keySet = selector.selectedKeys();
            for (SelectionKey key : keySet) {
                if (key.isReadable()) {//该事件是有客户端链接了
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    int len = socketChannel.read(buffer);
                    if(len>0){
                        if (isServer) {
                            proccessClientMessage(buffer);//如果是主机则处理从机发送过来的消息
                        }else{
                            proccessServerMessage(buffer);//如果是从机则处理主机发送过来的消息
                        }
                    }
                }
            }
        } catch (IOException e) {
           JOptionPane.showMessageDialog(this,"断开链接!");
           System.exit(0);
        }

        if (isServer) {
            syncInfoToClient();
        }
    }

    /**
     * 向服务端发送消息
     * @param message
     */
    public void sendMessageToServer(String message){
        try {
            clientSocketChannel.write(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 启动程序的执行
     */
    public void action() {
        KeyAdapter k = new KeyAdapter() { //不要求掌握
            /** 重写keyReleased()按键抬起事件 */
            public void keyReleased(KeyEvent e) { //当按键抬起时会自动触发--不要求掌握
                if (e.getKeyCode() == KeyEvent.VK_SPACE) { //若抬起的是空格键--不要求掌握
                    if (!isSingle && !isServer &&  clientSocketChannel != null) {
                        sendMessageToServer(CODE_SHOOT);
                        return;
                    }
                    if(ship.isLive()) {
                        Bomb obj = ship.shoot(); //获取深水炸弹对象
                        bombs = Arrays.copyOf(bombs, bombs.length + 1); //扩容
                        bombs[bombs.length - 1] = obj; //将obj添加到bombs的最后一个元素上
                    }
                }
                if (e.getKeyCode() == KeyEvent.VK_LEFT) { //若抬起的是左键头--不要求掌握
                    if (!isSingle && !isServer &&  clientSocketChannel != null) {
                        sendMessageToServer(CODE_MOVE_LEFT);
                        return;
                    }
                    ship.moveLeft(); //战舰左移
                }
                if (e.getKeyCode() == KeyEvent.VK_RIGHT) { //若抬起的是右键头--不要求掌握
                    if (!isSingle && !isServer && clientSocketChannel != null) {
                        sendMessageToServer(CODE_MOVE_RIGHT);
                        return;
                    }
                    ship.moveRight(); //战舰右移
                }
            }
        };
        this.addKeyListener(k); //不要求掌握

        Timer timer = new Timer(); //定时器对象
        int interval = 10; //定时间隔(以毫秒为单位)
        timer.schedule(new TimerTask() {
            public void run() { //定时干的事(每10毫秒自动发生)
                doNetWork();            //进行一次网络通讯
                if(isSingle||isServer) {
                    submarineEnterAction(); //潜艇(侦察潜艇、鱼雷潜艇、水雷潜艇)入场
                    mineEnterAction();      //水雷入场
                    moveAction();           //海洋对象移动
                    outOfBoundsAction();    //删除越界的海洋对象
                    bombBangAction();       //深水炸弹与潜艇的碰撞
                    mineBangAction();       //水雷与战舰的碰撞
                    checkGameOverAction();  //检测游戏结束
                }
                repaint();              //重画(重新调用paint()画)
            }
        }, interval, interval); //定时计划
    }

    /**
     * 重写paint()画  g:画笔
     */
    public void paint(Graphics g) { //每10毫秒走一次
        if(!isSingle&&!isServer&&syncInfo!=null){
            state = syncInfo.state;
            ship = syncInfo.ship;
            ship_2p = syncInfo.ship_2p;
            submarines = syncInfo.submarines;
            mines = syncInfo.mines;
            bombs = syncInfo.bombs;
            bombs_2p = syncInfo.bombs_2p;
            score = syncInfo.score;
        }
        switch (state) { //根据当前状态做不同的处理
            case GAME_OVER: //游戏结束状态时，画游戏结束图
                Images.gameover.paintIcon(null, g, 0, 0);
                break;
            case RUNNING: //运行状态时，画海洋图、对象、分和命
                Images.sea.paintIcon(null, g, 0, 0); //画海洋图
                ship.paintImage(g,isSingle?"":"1P"); //画战舰
                if(!isSingle){
                    ship_2p.paintImage(g,"2P");
                }
                for (int i = 0; i < submarines.length; i++) { //遍历潜艇数组
                    submarines[i].paintImage(g); //画潜艇
                }
                for (int i = 0; i < mines.length; i++) {
                    mines[i].paintImage(g);
                }
                for (int i = 0; i < bombs.length; i++) {
                    bombs[i].paintImage(g);
                }
                if(!isSingle) {
                    for (int i = 0; i < bombs_2p.length; i++) {
                        bombs_2p[i].paintImage(g);
                    }
                }
                g.drawString("SCORE: " + score, 200, 50); //画分------------不要求掌握
                g.drawString("LIFE: " + ship.getLife(), 400, 50); //画命----不要求掌握
                if(!isSingle){
                    g.drawString("2P_LIFE: " + ship_2p.getLife(), 500, 50);
                }
        }
    }

    public void start(){
        if(JOptionPane.showConfirmDialog(this,"是否联机?")==JOptionPane.YES_OPTION){//需要联机
            isSingle = false;
            if(JOptionPane.showConfirmDialog(this,"是否为主机?")==JOptionPane.YES_OPTION){
                isServer = true;
                try {
                    selector = Selector.open();
                    serverSocketChannel = ServerSocketChannel.open();
                    serverSocketChannel.bind(new InetSocketAddress(8088));
                    System.out.println("wait for client...");
                    clientSocketChannel = serverSocketChannel.accept();
                    //设置该通道为非阻塞
                    clientSocketChannel.configureBlocking(false);
                    //注册选择器，感兴趣的事件是可以读取客户端发送过来的消息
                    clientSocketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("client have connected!!");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                String input = JOptionPane.showInputDialog(this,"请输入主机IP地址");
                try {
                    selector = Selector.open();
                    clientSocketChannel = SocketChannel.open();
                    System.out.println("connect server...");
                    clientSocketChannel.connect(new InetSocketAddress(input, 8088));
                    clientSocketChannel.configureBlocking(false);
                    clientSocketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("server connected!" + clientSocketChannel.isConnected());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        action(); //启动程序的执行
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame(); //3.
        World world = new World();
        world.setFocusable(true);
        frame.add(world);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(WIDTH + 16, HEIGHT + 39);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true); //1)设置窗口可见  2)尽快调用paint()方法

        world.start();



    }
}

























